package zdream.control.world;

import zdream.control.assets.FoeDescription;
import zdream.control.foe.mmcommon.Ashes;
import zdream.control.foe.mmcommon.AshesLeader;
import zdream.control.input.WorldInput;
import zdream.control.region.FoeSpawnPoint;
import zdream.control.region.ItemSpec;
import zdream.control.region.Terrains;
import zdream.control.world.buff.*;
import zdream.utils.math.Rectangle;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static zdream.control.world.MFoeConstants.*;

/**
 * <p>角色
 * </p>
 *
 * @author Zdream
 * @since 0.0.1
 * @date 2019-06-09 (create)
 * @date 2023-12-25 (last modified)
 */
public abstract class MFoe extends Box implements IHookStorage {
	private static int ID = 1;
	public final int id;

	public static final Rectangle tmpRect = new Rectangle();

	/**
	 * 可能为 null
	 */
	public FoeDescription description;

	/**
	 * 一般为 null. 只有体型很大的 {@link MFoe}, 有多个 {@link Box} 时才有这个
	 */
	public MShadow[] shadows;

	/**
	 * 一般的 {@link MFoe} 是 {@link LevelWorld} 通过反射来创建的.
	 * @see LevelWorld#createFoe(FoeSpawnPoint)
	 */
	public MFoe(LevelWorld world) {
		synchronized (MFoe.class) {
			this.id = ID ++;
		}
		this.world = Objects.requireNonNull(world);
		this.createTime = world.ticker.activeTime;
		this.active = !world.ticker.foePaused;
		prepareBuffs();
		prepareTerrain();
	}

	/**
	 * 现在的想法是把它整成 package final 方法
	 */
	protected void prepare(ItemSpec... parameters) {
		for (ItemSpec param : parameters) {
			Objects.requireNonNull(param);
			if (param.xSet) {
				setAnchorX(param.x);
			}
			if (param.ySet) {
				setAnchorY(param.y);
			}
			if (param.orientation != null) {
				orientation = param.orientation;
			}

			if (param.type != null) {
				this.type = param.type;
			}
			if (param.side != null) {
				this.side = param.side;
			}
		}

		// 因为不能有重复的 patterns, 所以这里如果想执行起来快一点, 倒着扫描就行
		final int length = parameters.length;
		boolean applied = false;
		final HashSet<String> names = new HashSet<>();
		for (int i = length - 1; i >= 0; i--) {
			ItemSpec param = parameters[i];
			if (param.patterns == null) {
				continue;
			}
			for (ItemSpec.Pattern p : param.patterns) {
				if (names.contains(p.name)) {
					continue;
				}
				names.add(p.name);
				final Pattern pattern = this.createPattern(p.name)
						.withRectangle(p.x, p.y, p.width, p.height);
				if (p.base && !applied) {
					pattern.apply();
					applied = true;
				}
			}
		}
	}

	public void init() {
		// do nothing
	}

	/* ************
	 *  Category  *
	 ************ */
	/**
	 * 在这里, foe name 就是 category.
	 */
	public abstract String foeName();

	public boolean is(String category) {
		return foeName().equals(category) || type.equals(category);
	}

	public boolean not(String category) {
		return !is(category);
	}

	/**
	 * 角色类型. 包括但不限于:
	 *
	 * <li>重要角色 "leader": 含玩家控制角色、重要官邸 BOSS 以及有分量的 NPC
	 * <li>小怪 "foe": 含敌方小怪以及我方、敌方、中立方等召唤的喽啰
	 * <li>精英 "elite": 高级小怪
	 * <li>子弹 "bullet": 含小怪以及重要角色释放的具有攻击或触发效果的物体角色
	 * <li>陷阱 "trap": 除了静态地形以外的机关等
	 * <li>道具 "mass": 含掉落物、购买商品以及其它可拾取物品,
	 *         怪物或角色死后的收尾画面及图像
	 * <li>场 "field": 虚拟区域, 当目标角色接触后则激活的虚拟角色
	 * <li>贴图 "texture": 也包括剑气等传统意义的近战武器释放的物体, 以及其它没有实际意义的物体
	 * </li>
	 */
	public static final String TYPE_LEADER = "leader";
	public static final String TYPE_FOE = "foe";
	public static final String TYPE_ELITE = "elite";
	public static final String TYPE_BULLET = "bullet";
	public static final String TYPE_TRAP = "trap";
	public static final String TYPE_MASS = "mass";
	public static final String TYPE_FIELD = "field";
	public static final String TYPE_TEXTURE = "texture";
	public String type = TYPE_FOE;

	/* ***********
	 *  Pattern  *
	 *********** */
	public static final String PATTERN_NORMAL = "normal";
	public final List<Pattern> patterns = new ArrayList<>();
	public Pattern currentPattern;

	public class Pattern {
		public final String name;
		public Rectangle rectangle = new Rectangle();

		public Pattern(String name) {
			this.name = Objects.requireNonNull(name);
		}

		public Pattern withRectangle(float x, float y, float width, float height) {
			this.rectangle.set(x, y, width, height);
			return this;
		}

		public Pattern apply() {
			currentPattern = this;
			setBox(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
			flush();
			return this;
		}
	}

	public Pattern createPattern(String name) {
		Pattern pattern = new Pattern(name);
		patterns.add(pattern);
		return pattern;
	}

	public void applyPattern(String name) {
		if (currentPattern.name.equals(name)) {
			return;
		}

		Pattern newPattern = patterns.stream()
				.filter(p -> p.name.equals(name))
				.findAny()
				.orElseThrow(() -> new IllegalStateException("Fail to find pattern: " + name));

		final MHooks.FoeStageUpdateHookContext context = triggerHook(MHookType.UPDATE_PATTERN).as();
		context.withFoe(this)
				.withChanged(currentPattern.name, newPattern.name)
				.run();
		if (context.aborted) {
			System.out.printf("%s 的状态转变 (%s->%s) 被 %s 阻止了%n",
					this, currentPattern.name, newPattern.name, context.abortBy);
			return;
		}

		newPattern.apply();
	}

	public Pattern createNormalPattern() {
		return createPattern(PATTERN_NORMAL);
	}

	/* ************
	 * Life Cycle *
	 ************ */
	private final transient List<Consumer<MFoe>> cleans = new ArrayList<>();
	public DestroyCause destroyCause;

	public boolean isDestroyed() {
		return destroyCause != null;
	}

	public enum DestroyCause {
		ZERO_HP,
		SUICIDE, // 一般子弹会用这个, 飞狗被召唤回去也是这个
		OUTSIDE,
		THORN, // 即死的诸如刺、激光、岩浆等
		SQUEEZED, // 被方块挤死
	}

	public void destroy() {
		this.destroy(DestroyCause.SUICIDE);
	}

	public void destroy(DestroyCause cause) {
		if (destroyCause != null) {
			throw new IllegalStateException(this + " 已经被删除了");
		}
		destroyCause = cause;

		for (Consumer<MFoe> clean : cleans) {
			clean.accept(this);
		}
	}

	/**
	 * 注册一个在 {@link MFoe} 即将删除时调用的方法
	 * @return 返回一个句柄, 调用后会注销这个删除方法
	 */
	public Supplier<Boolean> onClean(Consumer<MFoe> handler) {
		cleans.add(handler);
		return () -> cleans.remove(handler);
	}

	/* **********
	 *  World   *
	 ********** */
	public final LevelWorld world;
	public final long createTime;

	/**
	 * 补充一个标记来表明 {@link MFoe} 是否是自由行动的.
	 * <p>在以下情况下, 标记设置为 false:
	 * <li>{@link zdream.control.region.Room} 切换时
	 * <li>{@link MFoe} 在剧情中 / 对话中等不受控情况
	 * </li>
	 * </p>
	 *
	 * <br/>
	 * <p>在非 active 情况下, 以下功能受限:
	 * <li>{@link MHookType#TICK} 将不再触发, 转而执行 {@link MHookType#TICK_WHEN_FOE_INACTIVE}
	 * <li>只有特殊标记的 {@link MHookType#RECEIVE_DAMAGE} 和 {@link MHookType#RECOVER}
	 * 才能发动.
	 * </li>
	 * </p>
	 */
	public boolean active;
	public long age;
	public TerrainHandler terrainHandler;
	private boolean gravityNeedUpdate = true;

	public TerrainHandler getTerrainHandler() {
		if (this.terrainHandler == null) {
			return world.defaultHandler;
		}
		return terrainHandler;
	}

	public void submitMotion(float deltaX, float deltaY) {
		this.getTerrainHandler().submitMotion(this, deltaX, deltaY);
	}

	@Override
	public String toString() {
		return String.format("%s#%d: (%4.2f,%4.2f)", foeName(), id, anchorX, anchorY);
	}

	private void prepareTerrain() {
		onHook(MHookType.ADD_BUFF)
				.withPriority(200)
				.handle(this::checkAddAndRemoveGravityBuff)
				.buildAndSave();
		onHook(MHookType.REMOVE_BUFF)
				.withPriority(200)
				.handle(this::checkAddAndRemoveGravityBuff)
				.buildAndSave();
		onHook(MHookType.TICK)
				.withPriority(-300)
				.handle(this::checkCurrentTerrain)
				.buildAndSave();
	}

	private void checkCurrentTerrain(MHooks.MHookContext context) {
		checkTerrainsAroundFoe();
		if (isDestroyed()) {
			context.abort();
			return;
		}

		if (!hasBuff(SinkInWaterBuff.CATEGORY) && SinkInWaterBuff.inWater(this)) {
			addBuff(new SinkInWaterBuff(this));
		}
		refreshGravityIfNeeded();
	}

	private void checkTerrainsAroundFoe() {
		if (terrainHandler == null || terrainHandler.floating) {
			return;
		}

		for (Iterator<TerrainHandler.TerrainParam> it = terrainHandler.checkBoxTerrain(this); it.hasNext(); ) {
			TerrainHandler.TerrainParam def = it.next();

			if (Terrains.isStab(def.terrain)) {
				Terrains.getStabRange(def.terrain, def.bx, def.by, tmpRect);
				if (tmpRect.overlaps(pos)) {
					triggerStabDamage();
				}
			}

			if (isDestroyed()) {
				return;
			}
		}
	}

	private void triggerStabDamage() {
		final HealthModule buff = findOneBuff(HealthModule.CATEGORY);
		if (buff == null) {
			return;
		}

		final MHooks.DamageContext context = this.triggerHook(MHookType.RECEIVE_DAMAGE).as();
		int hpMax = buff.hpMax;
		context.withAttacker(world.global)
				.withDefender(this)
				.withDamage(hpMax, DAMAGE_LEVEL_STAB)
				.run();
	}

	private void checkAddAndRemoveGravityBuff(MHooks.MHookContext rawContext) {
		MHooks.BuffContext context = rawContext.as();
		if (context.buff.not(MFoeConstants.CATEGORY_GRAVITY_ASSOCIATED)) {
			return;
		}
		gravityNeedUpdate = true;
	}

	public void refreshGravityIfNeeded() {
		if (!gravityNeedUpdate) {
			return;
		}

		final MHooks.GravityRefreshContext context = triggerHook(MHookType.REFRESH_GRAVITY).as();
		context.withFoe(this).withGravityScale(1, true).run();

		this.gravityScale = context.scale;
		this.gravityDown = context.down;
		this.gravityNeedUpdate = false;
	}

	/* **********
	 *   行动   *
	 ********** */

	/**
	 * 行动. 只调用盒子模块
	 * @param ticker
	 *   时间相关的参数
	 */
	public void tick(Ticker ticker) {
		age = ticker.activeTime - createTime;
		lastAnchorX = anchorX;
		lastAnchorY = anchorY;

		MHookType type = active ? MHookType.TICK : MHookType.TICK_WHEN_FOE_INACTIVE;
		final MHooks.FoeHookContext context = triggerHook(type).as();
		context.withFoe(this).run();
	}

	/* **********
	 *  Buffs   *
	 ********** */
	/**
	 * 这里将之前的 Modules 的概念全部合并进 buff.
	 */
	public final List<MFoeBuff> buffs = new ArrayList<>();
	protected final transient MHookStore hookStore = new MHookStore();

	private void prepareBuffs() {
		onHook(MHookType.RECEIVE_DAMAGE)
				.withPriority(-1)
				.handle(this::blockDamageOnInactiveState)
				.buildAndSave();
		onHook(MHookType.RECOVER)
				.withPriority(-1)
				.handle(this::blockHealingOnInactiveState)
				.buildAndSave();
	}

	private void blockDamageOnInactiveState(MHooks.MHookContext rawContext) {
		if (active) {
			return;
		}
		final MHooks.DamageContext context = rawContext.as();
		if (!context.executeWhenFoeIsInactive) {
			context.abort();
		}
	}

	private void blockHealingOnInactiveState(MHooks.MHookContext rawContext) {
		if (active) {
			return;
		}
		final MHooks.RecoverContext context = rawContext.as();
		if (!context.executeWhenFoeIsInactive) {
			context.abort();
		}
	}

	public void addBuff(MFoeBuff buff) {
		final MHooks.BuffContext context = triggerHook(MHookType.ADD_BUFF).as();
		context.withFoe(this).withBuff(buff).run();

		if (context.aborted) {
			System.out.printf("%s 被 %s 阻止添加到 %s 上%n", buff, context.abortBy, this);
			return;
		}

		this.buffs.add(buff);
		buff.init();
	}

	public void removeBuff(MFoeBuff buff) {
		final MHooks.BuffContext context = triggerHook(MHookType.REMOVE_BUFF).as();
		context.withFoe(this).withBuff(buff).run();

		if (context.aborted) {
			System.out.printf("%s 被 %s 阻止从 %s 上删除%n", buff, context.abortBy, this);
			return;
		}

		buff.destroy();
		this.buffs.remove(buff);
	}

	public boolean hasBuff(String category) {
		return this.buffs.stream().anyMatch(buff -> buff.is(category));
	}

	public List<MFoeBuff> findBuffs(String category) {
		return this.buffs.stream().filter(buff -> buff.is(category)).collect(Collectors.toList());
	}

	@SuppressWarnings("unchecked")
	public <T extends MFoeBuff> T findOneBuff(String category) {
		return (T) this.buffs.stream().filter(buff -> buff.is(category)).findAny().orElse(null);
	}

	public boolean confirm(String buffCategory, String tag) {
		MFoeBuff buff = findOneBuff(buffCategory);
		return buff != null && buff.is(tag);
	}

	public boolean deny(String buffCategory, String tag) {
		MFoeBuff buff = findOneBuff(buffCategory);
		return buff != null && buff.not(tag);
	}

	@Override
	public MHookStore.HookHandlerBuilder registerHook() {
		return hookStore.hookBuilder();
	}

	protected MHookStore.HookHandlerBuilder onHook(MHookType type) {
		return registerHook().withType(type);
	}

	@Override
	public MHooks.MHookContext triggerHook(MHookType type) {
		return MHooks.triggerHook(type, this.hookStore);
	}

	@Override
	public MHookStore getHookStore() {
		return hookStore;
	}

	public HealthModule.HealthModuleFactory buildHealthModule() {
		return HealthModule.builder(this);
	}

	public AttackModule.AttackModuleFactory buildAttackModule() {
		return AttackModule.builder(this);
	}

	public JumpModule.JumpModuleFactory buildJumpModule() {
		return new JumpModule.JumpModuleFactory(this);
	}

	public MassModule.MassModuleFactory buildMassModule() {
		return new MassModule.MassModuleFactory(this);
	}

	public StageModule.StageModuleFactory buildStageModule() {
		return new StageModule.StageModuleFactory(this);
	}

	public void putInput(WorldInput input) {
		findBuffs(ControlModule.CATEGORY)
				.stream()
				.map(buff -> (ControlModule) buff)
				.forEach(module -> module.input = input);
	}

	/* **********
	 *   灰烬   *
	 ********** */
	/*
	 * 怪物死了之后会产生 Ashes 类的 Foe
	 */
	{
		onClean(this::createAshesIfNeeded);
	}

	public void createAshesIfNeeded(MFoe self) {
		switch (self.destroyCause) {
		case ZERO_HP: case THORN: case SQUEEZED:
			break;
		default:
			return;
		}

		float x = getCenterX();
		float y = getCenterY();

		if (TYPE_FOE.equals(self.type) || TYPE_ELITE.equals(self.type)) {
			Ashes ashes = createFoeAshes();
			if (ashes == null) {
				return;
			}

			world.createFoe(ashes, x, y, this.orientation);
		} else if (TYPE_LEADER.equals(self.type)) {
			float v = 8, v2 = v / 2, s2 = (float) Math.cos(Math.PI / 4);
			float[][] vs = new float[][] {
				{v, 0}, {-v, 0}, {0, v}, {0, -v},
				{v * s2, v * s2}, {-v * s2, v * s2}, {-v * s2, -v * s2}, {v * s2, -v * s2},
				{v2, 0}, {-v2, 0}, {0, v2}, {0, -v2},
				{v2 * s2, v2 * s2}, {-v2 * s2, v2 * s2}, {-v2 * s2, -v2 * s2}, {v2 * s2, -v2 * s2},
			};
			for (float[] fs : vs) {
				AshesLeader ashes = new AshesLeader(this, fs[0], fs[1]);
				world.createFoe(ashes, x, y, this.orientation);
			}
			// 它是直接产生掉落物
			if (hasBuff(SpoilsHolder.CATEGORY)) {
				SpoilsHolder holder = findOneBuff(SpoilsHolder.CATEGORY);
				String foeName = holder.select();
				if (foeName != null) {
					world.createFoe(foeName, x, y, this.orientation);
				}
			}
		}
	}

	protected Ashes createFoeAshes() {
		Ashes ashes = new Ashes(this);

		if (hasBuff(SpoilsHolder.CATEGORY)) {
			ashes.spoilsBuilder = findOneBuff(SpoilsHolder.CATEGORY);
		} else {
			ashes.spoilsBuilder = SpoilsHolder.createDefault(this);
		}
		return ashes;
	}

	/* **********
	 *  Other   *
	 ********** */

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		MFoe mFoe = (MFoe) o;
		return id == mFoe.id;
	}

	@Override
	public int hashCode() {
		return id;
	}
}
